package fr.upmc.dtgui.reflexion;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.StringTokenizer;

import fr.upmc.dtgui.annotations.BooleanActuatorData;
import fr.upmc.dtgui.annotations.BooleanSensorData;
import fr.upmc.dtgui.annotations.IntegerActuatorData;
import fr.upmc.dtgui.annotations.IntegerSensorData;
import fr.upmc.dtgui.annotations.RealActuatorData;
import fr.upmc.dtgui.annotations.RealSensorData;
import javassist.CtClass;
import javassist.CtMethod;

/**
 * <b>AnnotationParser est la classe chargée de parcourir les annotations du
 * robot.</b>
 * <p>
 * Cette classe permet de paramétrer la génération de la station de
 * téléopération du robot ainsi que les ajouts nécessaire au robot selon les
 * annotations du robot.
 * </p>
 * 
 * @see java.lang.annotation.Annotation
 * @see fr.upmc.dtgui.annotations.BooleanActuatorData
 * @see fr.upmc.dtgui.annotations.RealActuatorData
 * @see fr.upmc.dtgui.annotations.IntegerActuatorData
 * @see fr.upmc.dtgui.annotations.BooleanSensorData
 * @see fr.upmc.dtgui.annotations.RealSensorData
 * @see fr.upmc.dtgui.annotations.IntegerSensorData
 * @see fr.upmc.dtgui.annotations.MeasurementUnit
 * @see fr.upmc.dtgui.annotations.RealRange
 * @see fr.upmc.dtgui.annotations.IntegerRange
 * @see fr.upmc.dtgui.annotations.VariationType
 * @see fr.upmc.dtgui.annotations.WithActuators
 * @see fr.upmc.dtgui.annotations.WithSensors
 * 
 * @author Ayoub Benali, Maxime Sarilar
 */

public class AnnotationParser {

	// Détermine si le robot a l'annotation WithSensor:
	public static boolean isWithSensors = false;
	// Détermine si le robot a l'annotation WithActuator:
	public static boolean isWithActuators = false;
	// Détermine si le robot peut donner sa vitesse:
	public static boolean hasGetSpeed = false;
	// Détermine si le robot peut donner son steering:
	public static boolean hasGetSteering = false;
	// Détermine si le robot peut donner son energie:
	public static boolean hasGetEnergy = false;
	// Détermine si le robot peut avoir sa vitesse contrôlée:
	public static boolean hasSetSpeed = false;
	// Détermine si le robot peut avoir son steering contrôlé:
	public static boolean hasSetSteering = false;
	// Les fréquence d'envoi et de réception des Threads:
	public static double compatibleSensorValue, compatibleActuatorValue;
	// Un dictionnaire pour stocker les annotations selon leur type:
	public static HashMap<String, ArrayList<Object>> annotations = 
			new HashMap<String, ArrayList<Object>>();
	//Le nom de la méthode utilisée pour lire la vitesse pour le robot:
	public static String getSpeed = "";
	//Le nom de la méthode utilisée pour écrire la vitesse pour le robot:
	public static String setSpeed = "";
	//Le nom de la méthode utilisée pour lire le steering pour le robot:
	public static String getSteering = "";
	//Le nom de la méthode utilisée pour écrire le steering pour le robot:
	public static String setSteering = "";
	//Le nom de la méthode utilisée pour lire l'énergie pour le robot:
	public static String getEnergy = "";

	/**
	 * La méthode parseAnnotation.
	 * <p>
	 * Cette méthode va parcourir toutes les annotations du robot.
	 * </p>
	 * 
	 * @param ct
	 *            La CtClass du robot.
	 */
	public static void parseAnnotation(CtClass ct) throws Exception {
		// Les listes qui vont permettre de calculer les fréquences des threads:
		ArrayList<Double> minValuesSensor = new ArrayList<Double>();
		ArrayList<Double> maxValuesSensor = new ArrayList<Double>();
		ArrayList<Double> minValuesActuator = new ArrayList<Double>();
		ArrayList<Double> maxValuesActuator = new ArrayList<Double>();

		// Construction du dictionnaire en prévision de le remplir:
		annotations.put("BooleanSensorData", new ArrayList<Object>());
		annotations.put("IntegerSensorData", new ArrayList<Object>());
		annotations.put("RealSensorData", new ArrayList<Object>());

		annotations.put("BooleanActuatorData", new ArrayList<Object>());
		annotations.put("IntegerActuatorData", new ArrayList<Object>());
		annotations.put("RealActuatorData", new ArrayList<Object>());

		// On récupère toutes les annotations du robot:
		Object classAnnotations[] = ct.getAnnotations();
		/*
		 * D'abord, on vérifie si le robot a les annotations WithSensors et
		 * WithActuators:
		 */
		if (classAnnotations.length > 0) {
			for (int i = 0; i < classAnnotations.length; i++)
				if (classAnnotations[i].toString().endsWith("WithSensors"))
					isWithSensors = true;
				else if (classAnnotations[i].toString().endsWith(
						"WithActuators"))
					isWithActuators = true;
		}

		// On récupère toutes les méthodes du robot:
		CtMethod methods[] = ct.getMethods();

		// Initialisation des booléens:
		boolean robotAnnotationSensor = false;
		boolean robotAnnotationActuator = false;
		
		//Listes utilisées pour les noms des méthodes d'écriture/lecture:
		ArrayList<String> groupNamesSensor = new ArrayList<String>();
		ArrayList<String> groupNamesActuator = new ArrayList<String>();
		getSpeed = "";
		setSpeed = "";
		getSteering = "";
		setSteering = "";
		getEnergy = "";

		for (int i = 0; i < methods.length; i++) {
			// On récupère toutes les annotations de la méthode courante:
			Object methodAnnotation[] = methods[i].getAnnotations();

			for (int j = 0; j < methodAnnotation.length; j++) {
				/*
				 * On va maintenant remplir le dictionnaire des annotations,
				 * ainsi que les listes permettant de calculer les fréquences:
				 */
				if (methodAnnotation[j] instanceof BooleanSensorData) {
					BooleanSensorData bsd = 
							(BooleanSensorData) methodAnnotation[j];
					minValuesSensor.add(bsd.minReadingRate());
					maxValuesSensor.add(bsd.maxReadingRate());
					robotAnnotationSensor = true;
					annotations.get("BooleanSensorData").add(bsd);
					groupNamesSensor.add(
							bsd.groupName()+"@"+methods[i].getName());

				} else if (methodAnnotation[j] instanceof IntegerSensorData) {
					IntegerSensorData isd = 
							(IntegerSensorData) methodAnnotation[j];
					minValuesSensor.add(isd.minReadingRate());
					maxValuesSensor.add(isd.maxReadingRate());
					robotAnnotationSensor = true;
					annotations.get("IntegerSensorData").add(isd);
					groupNamesSensor.add(
							isd.groupName()+"@"+methods[i].getName());
				} else if (methodAnnotation[j] instanceof RealSensorData) {
					RealSensorData rsd = 
							(RealSensorData) methodAnnotation[j];
					minValuesSensor.add(rsd.minReadingRate());
					maxValuesSensor.add(rsd.maxReadingRate());
					robotAnnotationSensor = true;
					annotations.get("RealSensorData").add(rsd);
					groupNamesSensor.add(
							rsd.groupName()+"@"+methods[i].getName());
				} else if (methodAnnotation[j] instanceof BooleanActuatorData) {
					BooleanActuatorData bad = 
							(BooleanActuatorData) methodAnnotation[j];
					minValuesActuator.add(bad.minWritingRate());
					maxValuesActuator.add(bad.maxWritingRate());
					robotAnnotationActuator = true;
					annotations.get("BooleanActuatorData").add(bad);
					groupNamesActuator.add(
							bad.groupName()+"@"+methods[i].getName());
				} else if (methodAnnotation[j] instanceof IntegerActuatorData) {
					IntegerActuatorData iad = 
							(IntegerActuatorData) methodAnnotation[j];
					minValuesActuator.add(iad.minWritingRate());
					maxValuesActuator.add(iad.maxWritingRate());
					robotAnnotationActuator = true;
					annotations.get("IntegerActuatorData").add(iad);
					groupNamesActuator.add(
							iad.groupName()+"@"+methods[i].getName());
				} else if (methodAnnotation[j] instanceof RealActuatorData) {
					RealActuatorData ras = 
							(RealActuatorData) methodAnnotation[j];
					minValuesActuator.add(ras.minWritingRate());
					maxValuesActuator.add(ras.maxWritingRate());
					robotAnnotationActuator = true;
					annotations.get("RealActuatorData").add(ras);
					groupNamesActuator.add(
							ras.groupName()+"@"+methods[i].getName());
				}
			}
			
			manageWriteMethod(groupNamesActuator);
			manageReadMethod(groupNamesSensor);

			// Calcul des fréquences de lecture:
			if (robotAnnotationSensor)
				compatibleSensorValue = (getMaxValue(minValuesSensor) 
						+ getMinValue(maxValuesSensor)) / 2;
			// Calcul des fréquences d'écriture:
			if (robotAnnotationActuator)
				compatibleActuatorValue = (getMaxValue(minValuesActuator) 
						+ getMinValue(maxValuesActuator)) / 2;		
		}
	}

	/**
	 * La méthode getMinValue.
	 * <p>
	 * Cette méthode va retourner la plus petite valeur d'une liste.
	 * </p>
	 * 
	 * @param list
	 *            La liste à parcourir.
	 * @return La plus petite valeur de la liste.
	 * 
	 * @see java.util.ArrayList
	 */
	private static Double getMinValue(ArrayList<Double> list) {
		Double ret;
		ret = list.get(0);
		for (Double d : list)
			if (ret.doubleValue() > d.doubleValue())
				ret = d;
		return ret;
	}

	/**
	 * La méthode getMaxValue.
	 * <p>
	 * Cette méthode va retourner les plus grande valeur d'une liste.
	 * </p>
	 * 
	 * @param list
	 *            La liste à parcourir.
	 * @return La plus grande valeur de la liste.
	 * 
	 * @see java.util.ArrayList
	 */
	private static Double getMaxValue(ArrayList<Double> list) {
		Double ret;
		ret = list.get(0);
		for (Double d : list)
			if (ret.doubleValue() < d.doubleValue())
				ret = d;
		return ret;
	}
	
	/**
	 * La méthode manageWriteMethod.
	 * <p>
	 * Cette méthode va changer les valeurs des champs de la classe
	 * AnnotationParser qui seront utilisés lors de la création de la station
	 * de téléopération.
	 * </p>
	 * 
	 * @param list
	 *            La liste contenant les chaînes de caractères sous la forme:
	 *            [groupeName]@[methodName]
	 *            Par exemple:
	 *            speed@getSpeed
	 * 
	 * @see java.util.ArrayList
	 */
	private static void manageWriteMethod(ArrayList<String> list) {
		for(String s : list) {
			StringTokenizer token = new StringTokenizer(s, "@");
			String groupName = token.nextToken();
			if(groupName.equals("speed")) {
				hasSetSpeed = true;
				setSpeed = token.nextToken();
			} else if(groupName.equals("steering")) {
				hasSetSteering = true;
				setSteering = token.nextToken();
			} 
		}
	}
	
	/**
	 * La méthode manageReadMethod.
	 * <p>
	 * Cette méthode va changer les valeurs des champs de la classe
	 * AnnotationParser qui seront utilisés lors de la création de la station
	 * de téléopération.
	 * </p>
	 * 
	 * @param list
	 *            La liste contenant les chaînes de caractères sous la forme:
	 *            [groupeName]@[methodName]
	 *            Par exemple:
	 *            speed@setSpeed
	 * 
	 * @see java.util.ArrayList
	 */
	private static void manageReadMethod(ArrayList<String> list) {
		for(String s : list) {
			StringTokenizer token = new StringTokenizer(s, "@");
			String groupName = token.nextToken();
			if(groupName.equals("speed")) {
				hasGetSpeed = true;
				getSpeed = token.nextToken();
			} else if(groupName.equals("steering")) {
				hasGetSteering = true;
				getSteering = token.nextToken();
			} else if(groupName.equals("energy")) {
				hasGetEnergy = true;
				getEnergy = token.nextToken();
			} 
		}
	}
}
